package org.apache.cordova;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.cordova.api.LOG;

/**
 * Very simple local storage implementation.
 * It uses serialization feature.
 * @author Cyril Joui
 */
public class LocalStorageImpl extends LocalStorage {

	/**
	 * UID.
	 */
	private static final long serialVersionUID = 1L;

	/**
	 * Filename to use.
	 * FIXME CJO - must use user's directory! and application folder ...
	 */
	private static final String FILENAME = ".localStorage";

	/**
	 * 5 sec delay to persist.
	 */
	private static final long PERSISTDELAY = 5000;

	/**
	 * Internal map storage.
	 */
	private Map<String, String> mapStorage;

	/**
	 * Persist timer.
	 * Use it to schedule saves.
	 */
	private Timer persistTimer;

	private TimerTask timerTask;

	/**
	 * Default constructor.
	 */
	public LocalStorageImpl() {
		persistTimer = new Timer();
		mapStorage = new HashMap<String, String>();
		load();
	}

	/**
	 * Load map storage.
	 */
	@SuppressWarnings("unchecked")
	private void load() {
		FileInputStream fileInputStream = null;
		try {
			fileInputStream = new FileInputStream(FILENAME);
			BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
			ObjectInputStream objectInputStream = new ObjectInputStream(bufferedInputStream);
			mapStorage = (Map<String, String>) objectInputStream.readObject();
			System.out.println("loaded mapStorage: " + mapStorage);
		} catch (FileNotFoundException e) {
			// No storage here
		} catch (IOException e) {
			e.printStackTrace();
			// Error reading local storage?
			LOG.e(getClass().getCanonicalName(), "Error reading local storage!", e);
		} catch (ClassNotFoundException e) {
			// Should be a HashMap, else bad storage file
			e.printStackTrace();
		} finally {
			if (fileInputStream != null) {
				try {
					fileInputStream.close();
				} catch (IOException e) {
				}
			}
		}
	}

	/**
	 * Persist map storage.
	 * Save mapStorage in a separated thread.
	 * @param now persist now or schedule it
	 */
	private void persist(boolean now) {
		if (timerTask != null) {
			timerTask.cancel();
		}
		// Task
		timerTask = new TimerTask() {
			@Override
			public void run() {
				LOG.d(getClass().getCanonicalName(), "Persisting local storage ...");
				FileOutputStream fileOutputStream = null;
				try {
					fileOutputStream = new FileOutputStream(FILENAME);
					BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
					ObjectOutputStream objectOutputStream = new ObjectOutputStream(bufferedOutputStream);
					objectOutputStream.writeObject(mapStorage);
					bufferedOutputStream.flush();
				} catch (FileNotFoundException e) {
					// Can't open file?
					e.printStackTrace();
				} catch (IOException e) {
					e.printStackTrace();
					// Error writing localstorage
					LOG.e(getClass().getCanonicalName(), "Error writing local storage ...", e);
				} finally {
					if (fileOutputStream != null) {
						try {
							fileOutputStream.close();
						} catch (IOException e) {
						}
					}
				}
			}
		};

		if (now) {
			TimerTask localTimerTask = timerTask;
			timerTask = null;
			localTimerTask.run();
		} else {
			persistTimer.schedule(timerTask, PERSISTDELAY);
		}
	}

	/**
	 * Persist not now.
	 */
	private void persist() {
		persist(false);
	}

	/**
	 * {@inheritDoc}
	 * @see org.apache.cordova.LocalStorage#getItem(java.lang.String)
	 */
	@Override
	public String getItem(String key) {
		return mapStorage.get(key);
	}

	/**
	 * {@inheritDoc}
	 * @see org.apache.cordova.LocalStorage#setItem(java.lang.String, java.lang.String)
	 */
	@Override
	public void setItem(String key, String value) {
		mapStorage.put(key, value);
		persist();
	}

	/**
	 * {@inheritDoc}
	 * @see org.apache.cordova.LocalStorage#removeItem(java.lang.String)
	 */
	@Override
	public void removeItem(String key) {
		if (!mapStorage.containsKey(key)) {
			return;
		}
		mapStorage.remove(key);
		persist();
	}

	/**
	 * {@inheritDoc}
	 * @see org.apache.cordova.LocalStorage#clear()
	 */
	@Override
	public void clear() {
		mapStorage.clear();
		persist();
	}

	/**
	 * {@inheritDoc}
	 * @see org.apache.cordova.LocalStorage#release()
	 */
	@Override
	protected void release() {
		persist(true);
		persistTimer.cancel();
		persistTimer.purge();
		persistTimer = null;
	}

}
